3. K8S实操之从入门到放弃(三)

6. 存储

Volume

Volume指的是存储卷,包含可被Pod中容器访问的数据目录。容器中的文件在磁盘上是临时存放的, 当容器崩溃时文件会丢失,同时无法在多个Pod中共享文件,通过使用存储卷可以解决这两个问题。 Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。 当 Pod 不再存在时,Kubernetes 也会销毁临时卷; 不过 Kubernetes 不会销毁持久卷。 对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢 失。

卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的不同卷 的类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。常用的卷类型有 configMap、emptyDir、local、nfs、secret 等。

使用方式

使用卷时, 在 .spec.volumes 字段中设置为 Pod 提供的卷,并在 .spec.containers[*].volumeMounts 字段中声明卷在容器中的挂载位置。 容器中的进程看到的文件系统视图是由它们的容器镜像的初始内 容以及挂载在容器中的卷(如果定义了的话)所组成的。 其中根文件系统同容器镜像的内容相吻合。 任何在该文件系统下的写入操作,如果被允许的话,都会影响接下来容器中进程访问文件系统时所看 到的内容。

apiVersion: v1
kind: Pod
metadata:
	name: configmap-pod
spec:
	containers:
		- name: test
		image: busybox:1.28
		volumeMounts:
			..........
	volumes:
		............

搭建nfs文件系统

nfs(network filesystem ): 网络文件存储系统

安装nfs-server


# 在每个机器。
yum install -y nfs-utils

# 在master 执行以下命令
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports

# 执行以下命令,启动 nfs 服务;创建共享目录
mkdir -p /nfs/data

# 在master执行
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server

# 使配置生效
exportfs -r

#检查配置是否生效
exportfs

配置nfs-client

# nfs-server节点的ip
showmount -e 192.168.11.101
mkdir -p /nfs/data
mount -t nfs 192.168.11.101:/nfs/data /nfs/data 

nfs方式数据挂载

相对于 emptyDir 和 hostPath,nfs这种 Volume 类型的最大特点就是不依赖 Kuberees Volume 的 底层基础设施由独立的存储系统管理,与 Kubernetes 集群是分离的。数据被持久化后,即使整个 Kubernetes 崩溃也不会受损。

apiVersion: apps/v1
kind: Deployment
metadata:
	labels:
		app: nginx-pv-demo
	name: nginx-pv-demo
spec:
	replicas: 2
	selector:
		matchLabels:
			app: nginx-pv-demo
	template:
		metadata:
			labels:
				app: nginx-pv-demo
		spec:
			containers:
			- image: nginx
				name: nginx
				volumeMounts:
				- name: html
					mountPath: /usr/share/nginx/html
			volumes:
			- name: html
				nfs:
					server: 192.168.11.101
					path: /nfs/data/nginx-pv

PV & PVC

Volume 提供了非常好的数据持久化方案,不过在可管理性上还有不足。前面 nfs 例子来说,要使用 Volume, Pod 必须事先知道以下信息:

但是 Pod 通常是由应用的开发人员维护,而 Volume 则通常是由存储系统的管理员维护。开发人员要 获得上面的信息,要么询问管理员,要么自己就是管理员。这样就带来一个管理上的问题:应用开发 人员和系统管理员的职责耦合在一起了。如果系统规模较小或者对于开发环境,这样的情况还可以接 受,当集群规模变大,特别是对于生产环境,考虑到效率和安全性,这就成了必须要解决的问题。

Kubernetes 给出的解决方案是 Persistent Volume 和 Persistent Volume Claim。

PersistentVolume(PV)是外部存储系统中的一块存储空间,由管理员创建和维护,将应用需要持久 化的数据保存到指定位置。与 Volume 一样,PV 具有持久性,生命周期独立于 Pod。

Persistent Volume Claim (PVC)是对 PV 的申请 (Claim),申明需要使用的持久卷规格。PVC 通常 由普通用户创建和维护。需要为 Pod 分配存储资源时,用户可以创建一个PVC,指明存储资源的容量 大小和访问模式 (比如只读)等信息,Kubernetes 会查找并提供满足条件的 PV。有了 PersistentVolumeClaim,用户只需要告诉 Kubernetes 需要什么样的存储资源,而不必关心真正的 空间从哪里分配、如何访问等底层细节信息。这些 Storage Provider 的底层信息交给管理员来处理, 只有管理员才应该关心创建 PersistentVolume 的细节信息。

基本使用

创建pv

apiVersion: v1
kind: PersistentVolume
metadata:
	name: nfs-pv
spec:
	capacity:
		storage: 1Gi #指定容量大小
	accessModes: # 访问模式
		- ReadWriteMany
	persistentVolumeReclaimPolicy: Retain
	storageClassName: nfs
	nfs:
		path: /{nfs-server目录名称}
		server: {nfs-server IP 地址}

值得注意的是,persistentVolumeReclaimPolicy只适用于一些类型的 PV,如 NFS、HostPath、 iSCSI 等。对于一些云平台提供的存储,如 AWS EBS 和 Azure Disk,由于底层提供商会自动处理 PV 的回收问题,因此该属性不适用。

创建pvc

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
	name: nfs-pvc
spec:
	accessModes:
		- ReadWriteMany
	resources:
		requests:
			storage: 1Gi
	storageClassName: nfs # 通过名字进行选择
	#selector: 通过标签形式
		# matchLabels:
		# pv-name: nfs-pv

静态供应示例

创建PV池

 #nfs主节点
 mkdir -p /nfs/data/01
 mkdir -p /nfs/data/02
 mkdir -p /nfs/data/03

创建PV

apiVersion: v1
kind: PersistentVolume
metadata:
	name: pv01-10m
spec:
	capacity:
		storage: 10M
	accessModes:
		- ReadWriteMany
	storageClassName: nfs
	nfs:
		path: /nfs/data/01
		server: 192.168.11.101
---
apiVersion: v1
kind: PersistentVolume
metadata:
	name: pv02-1gi
spec:
	capacity:
		storage: 1Gi
	accessModes:
		- ReadWriteMany
	storageClassName: nfs
	nfs:
		path: /nfs/data/02
		server: 192.168.11.101
---
apiVersion: v1
kind: PersistentVolume
metadata:
	name: pv03-3gi
spec
	capacity:
		storage: 3Gi
	accessModes:
		- ReadWriteMany
	storageClassName: nfs
	nfs:
		path: /nfs/data/03
		server: 192.168.11.101

image.png

创建PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
	name: nginx-pvc
spec:
	accessModes:
		- ReadWriteMany
	resources:
		requests:
			storage: 200Mi
	storageClassName: nfs

image.png

创建Pod绑定PVC

apiVersion: apps/v1
kind: Deployment
metadata:
	labels:
		app: nginx-deploy-pvc
	name: nginx-deploy-pvc
spec:
	replicas: 2
	selector:
		matchLabels:
			app: nginx-deploy-pvc
	template:
		metadata:
			labels:
				app: nginx-deploy-pvc
	spec:
		containers:
		- image: nginx
			name: nginx
			volumeMounts:
			- name: html
				mountPath: /usr/share/nginx/html
		volumes:
		- name: html
			persistentVolumeClaim:
				claimName: nginx-pvc

动态供应

在前面的例子中,我们提前创建了PV,然后通过 PVC 申请 PV 并在Pod 中使用,这种方式叫作静态供 应 ( Static Provision)与之对应的是动态供应 (Dynamical Provision),即如果没有满足PVC 条件的 PV,会动态创建 PV。相比静态供应,动态供应有明显的优势:不需要提前创建 PV,减少了管理员的 工作量,效率高。动态供应是通过 StorageClass 实现的,StorageClass 定义了如何创建 PV,但需要 注意的是每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV, 该字段必须指定才能实现动态创建。

配置动态供应的默认存储类

## 创建了一个存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
	name: nfs-storage
	annotations:
		storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
	archiveOnDelete: "true" ## 删除pv的时候,pv的内容是否要备份
  
---
apiVersion: apps/v1
kind: Deployment
metadata:
	name: nfs-client-provisioner
	labels:
		app: nfs-client-provisioner
	# replace with namespace where provisioner is deployed
	namespace: default
spec:
	replicas: 1
	strategy:
		type: Recreate
	selector:
		matchLabels:
			app: nfs-client-provisioner
	template:
		metadata:
			labels:
				app: nfs-client-provisioner
		spec:
			serviceAccountName: nfs-client-provisioner
			containers:
				- name: nfs-client-provisioner
					image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nfs-subdir-external-provisioner:v4.0.2
					# resources:
						# limits:
							# cpu: 10m
						# requests:
							# cpu: 10m
					volumeMounts:
						- name: nfs-client-root
						mountPath: /persistentvolumes
					env:
						- name: PROVISIONER_NAME
							value: k8s-sigs.io/nfs-subdir-external-provisioner
						- name: NFS_SERVER
							value: 192.168.11.101 ## 指定自己nfs服务器地址
						- name: NFS_PATH
							value: /nfs/data ## nfs服务器共享的目录
				volumes:
					- name: nfs-client-root
						nfs:
							server: 192.168.11.101
							path: /nfs/data
---
apiVersion: v1
kind: ServiceAccount
metadata:
	name: nfs-client-provisioner
	# replace with namespace where provisioner is deployed
	namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
	name: nfs-client-provisioner-runner
rules:
	- apiGroups: [""]
		resources: ["nodes"]
		verbs: ["get", "list", "watch"]
	- apiGroups: [""]
		resources: ["persistentvolumes"]
		verbs: ["get", "list", "watch", "create", "delete"]
	- apiGroups: [""]
		resources: ["persistentvolumeclaims"]
		verbs: ["get", "list", "watch", "update"]
	- apiGroups: ["storage.k8s.io"]
		resources: ["storageclasses"]
		verbs: ["get", "list", "watch"]
	- apiGroups: [""]
		resources: ["events"]
		verbs: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
	name: run-nfs-client-provisioner
subjects:
	- kind: ServiceAccount
	name: nfs-client-provisioner
	# replace with namespace where provisioner is deployed
	namespace: default
roleRef:
	kind: ClusterRole
	name: nfs-client-provisioner-runner
	apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
	name: leader-locking-nfs-client-provisioner
	# replace with namespace where provisioner is deployed
	namespace: default
rules:
	- apiGroups: [""]
		resources: ["endpoints"]
		verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
	name: leader-locking-nfs-client-provisioner
	# replace with namespace where provisioner is deployed
	namespace: default
subjects:
	- kind: ServiceAccount
		name: nfs-client-provisioner
		# replace with namespace where provisioner is deployed
		namespace: default
roleRef:
	kind: Role
	name: leader-locking-nfs-client-provisioner
	apiGroup: rbac.authorization.k8s.io

7. 配置

ConfigMap

在 Kubernetes 中,ConfigMap 是一种用于存储非敏感信息的 Kubernetes 对象。它用于存储配置 数据,如键值对、整个配置文件或 JSON 数据等。ConfigMap 通常用于容器镜像中的配置文件、命令 行参数和环境变量等。

ConfigMap 可以通过三种方式进行配置数据的注入:

  1. 环境变量注入:将配置数据注入到 Pod 中的容器环境变量中。
  2. 配置文件注入:将配置数据注入到 Pod 中的容器文件系统中,容器可以读取这些文件。
  3. 命令行参数注入:将配置数据注入到容器的命令行参数中。

优点

  1. 避免了硬编码,将配置数据与应用代码分离。
  2. 便于维护和更新,可以单独修改 ConfigMap 而不需要重新构建镜像。
  3. 可以通过多种方式注入配置数据,更加灵活。
  4. 可以通过 Kubernetes 的自动化机制对 ConfigMap 进行版本控制和回滚。
  5. ConfigMap 可以被多个 Pod 共享,减少了配置数据的重复存储。

定义 ConfigMap

# 查看 configmap![](Aspose.Words.b6136751-f596-4a4e-82ab-d1b23da879b7.036.png)
$ kubectl get configmap/cm  

# 查看详细
$ kubectl describe configmap/cm my-config 

# 删除 cm
$ kubectl delete cm my-config
kubectl create configmap my-config --from-literal=key1=value1 --from- ![](Aspose.Words.b6136751-f596-4a4e-82ab-d1b23da879b7.038.png)literal=key2=value2
apiVersion: v1
kind: ConfigMap
metadata:
	name: my-config
data:
	key1: value1
	key2: value2
---
apiVersion: v1
kind: ConfigMap
metadata:
	name: app-config
data:
	application.yml: |
		name: fox
echo-n admin >./username
echo -n 123456 > ./password
kubectl create configmap myconfigmap --from-file=./username --from-file=./password
kubectl create configmap my-config --from-file=config-files/
kubectl create configmap my-config --from-env-file=<env>

使用示例

# docker安装redis
docker run -v /data/redis/redis.conf:/etc/redis/redis.conf \
-v /data/redis/data:/data \
-d --name myredis \
-p 6379:6379 \
redis:latest  redis-server /etc/redis/redis.conf

创建ConfigMap

#创建redis.conf
daemonize yes 
requirepass root 4
# 创建配置,redis保存到k8s的etcd![](Aspose.Words.b6136751-f596-4a4e-82ab-d1b23da879b7.045.png)
kubectl create cm redis-conf --from-file=redis.conf 7
#查看资源清单
kubectl get cm redis-conf -oyaml

image.png

apiVersion: v1
kind: ConfigMap
metadata:
	name: redis-conf
data:
	redis.conf: |
		maxmemory-policy allkeys-lru
		requirepass root

创建Pod

apiVersion: v1
kind: Pod
metadata:
	name: redis
spec:
	containers:
	- name: redis
		image: redis
		command:
			- redis-server
			- "/redis-master/redis.conf" #指的是redis容器内部的位置
		ports:
			- containerPort: 6379
		volumeMounts:
		- mountPath: /data
			name: data
		- mountPath: /redis-master
			name: config
	volumes:
		- name: data
			emptyDir: {}
		- name: config
			configMap:
				name: redis-conf
				items:
					- key: redis.conf
						path: redis.conf

测试

kubectl exec -it redis -- redis-cli
127.0.0.1:6379> config get  maxmemory-policy 3

image.png

Secret

Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 SSH 密钥。 将这些信息放在 secret 中比放在 Pod 的定义或者 容器镜像 中来说更加安全和灵活。

在 Kubernetes 中,Secrets 通常被用于以下场景:

定义 Secret

kubectl create secret generic my-secret --from-literal=username=admin --from- literal![](Aspose.Words.b6136751-f596-4a4e-82ab-d1b23da879b7.047.png)=password=admin123
apiVersion: v1
kind: Secret
metadata:
	name: my-secret
type: Opaque
data:
	username: YWRtaW4= # base64 编码后的用户名 admin
	password: MWYyZDFlMmU2N2Rm # base64 编码后的密码 1f2d1e2e67df

注意: 这个 YAML 文件定义了一个名为 my-secret 的 Secret 对象,其中包含了两个 base64 编码后的 key-value 对:username 和 password。

echo -n admin >./username 
echo -n 123456 > ./password 
kubectl create secret generic mysecret --from-file=./username --from-file=./password
1 kubectl create secret generic  my-config --from-env-file=<env>![](Aspose.Words.b6136751-f596-4a4e-82ab-d1b23da879b7.049.png)

使用示例:从私有docker仓库拉取镜像

docker pull registry.cn-hangzhou.aliyuncs.com/fox666/tulingmall-product:0.0.5

无法从私有镜像仓库拉取镜像,抛出如下错误:

image.png

解决方案:使用 docker 的用户信息来生成 secret:

##命令格式

kubectl create secret docker-registry myregistrykey \
--docker-server=<你的镜像仓库服务器> \
--docker-username=<你的用户名> \
--docker-password=<你的密码> \
--docker-email=<你的邮箱地址>

kubectl create secret docker-registry myregistrykey --docker-server=registry.cn-hangzhou.aliyuncs.com --docker-username=fox666 --docker-password=xxx

在创建 Pod 的时候,通过imagePullSecrets来引用刚创建的myregistrykey

apiVersion: v1
kind: Pod
metadata:
	name: tulingmall-product
spec:
	containers:
	- name: tulingmall-product
		image: registry.cn-hangzhou.aliyuncs.com/fox666/tulingmall-product:0.0.5
	imagePullSecrets:
	- name: myregistrykey

8. Ingress

**Ingress 是一种 Kubernetes 资源类型,它允许在 Kubernetes 集群中暴露 HTTP 和 HTTPS 服务。 通过 Ingress,您可以将流量路由到不同的服务和端点,而无需使用不同的负载均衡器。Ingress 通常 使用 Ingress Controller 实现,它是一个运行在 Kubernetes 集群中的负载均衡器,它根据Ingress 规则 配置路由规则并将流量转发到相应的服务。

下面是一个将所有流量都发送到同一 Service 的简单 Ingress 示例:

image.png

Ingress 和 Service 区别

Ingress 和 Service都是 Kubernetes 中用于将流量路由到应用程序的机制,但它们在路由层面上有所 不同:

安装Ingress

1)下载ingress配置文件

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.47.0/deploy/static/provider/baremetal/deploy.yaml

[root@k8s-master k8s]# grep image: deploy.yaml
            image: k8s.gcr.io/ingress-nginx/controller:v0.46.0@sha256:52f0058bed0a17ab0fb35628ba97e8d52b5d32299fbc03cc0f6c7b9ff036b61a
             image: docker.io/jettech/kube-webhook-certgen:v1.5.1
             image: docker.io/jettech/kube-webhook-certgen:v1.5.1

# 修改镜像vi deploy.yaml
# 1、将image k8s.gcr.io/ingress-nginx/controller:v0.46.0@sha256:52f0058bed0a17ab0fb35628ba97e8d52b5d32299fbc03cc0f6c7b9ff036b61a的值改为如下值:
registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/ingress-nginx-controller:v0.46.0

2)安装ingress,执行如下命令

kubectl apply -f ingress-controller.yaml 
  1. 查看是否安装成功
kubectl get pod,svc -n ingress-nginx -owide

image.png

image.png

使用Ingress

官网地址:https://kubernetes.github.io/ingress-nginx/

配置ingress访问规则(就是类似配置nginx的代理转发配置),让ingress将域名tomcat.tuling.com 转发给后端的my-tomcat服务,新建一个文件ingress-tomcat.yaml,内容如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
	name: web-ingress
spec:
	rules:
	- host: tomcat.tuling.com #转发域名
		http:
			paths:
			- pathType: Prefix
				path: /
				backend:
					service:
						name: my-tomcat
						port:
							number: 8080 #service的端口

执行如下命令生效规则:

kubectl apply -f ingress-tomcat.yaml 

查看生效的ingress规则:

kubectl get ing

在访问机器配置host,win10客户机在目录:C:\Windows\System32\drivers\etc,在host里增加如 下host(ingress部署的机器ip对应访问的域名)

192.168.65.82

配置完后直接在客户机浏览器访问http://192.168.65.82:30940能正常访问tomcat。

Service&Ingress总结

Service 是 K8S 服务的核心,屏蔽了服务细节,统一对外暴露服务接口,真正做到了“微服务”。举 个例子,我们的一个服务 A,部署了 3 个备份,也就是 3 个 Pod;对于用户来说,只需要关注一个 Ser vice 的入口就可以,而不需要操心究竟应该请求哪一个 Pod。优势非常明显:一方面外部用户不需 要感知因为 Pod 上服务的意外崩溃、K8S 重新拉起 Pod 而造成的 IP 变更,外部用户也不需要感知 因升级、变更服务带来的 Pod 替换而造成的 IP 变化,另一方面,Service 还可以做流量负载均衡。 但是,Service 主要负责 K8S 集群内部的网络拓扑。集群外部需要用 Ingress 。 Ingress 是整个 K8S 集群的接入层,复杂集群内外通讯。

Ingress 和 Service 的网络拓扑关系图如下:

image.png